package org.thanatos.xcw.utils;

import org.apache.solr.client.solrj.SolrQuery;
import org.apache.solr.client.solrj.SolrServer;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.common.SolrDocument;
import org.apache.solr.common.SolrDocumentList;
import org.springframework.util.StringUtils;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 对Solr查询的包装，提供方便的工具
 *
 * Created by handoop on 2015/5/10.
 */
@SuppressWarnings("all")
public class SolrBeanHelper<T> {

    private List<String> mHighlightFields;
    private List<String> mQueryFields;
    private Map<String, List<String>> mDesignateFields;
    private List<String> mDesignateHighligntFields;
    private SolrQuery mSolrQuery;
    private SolrServer mSolrServer;
    private QueryResponse response;
    private SolrDocumentList mSolrList;
    private List<String> ids;
    private long mDocumentCount;
    private String query;
    private boolean isHighlight;
    private Map<String, Method> mMap;
    private Class<T> bean;
    private boolean analysable;

    public static final String OTHER_FIELD = "<:other:>";

    private SolrBeanHelper(Class<T> bean) {
        this.bean = bean;
        this.analysable = false;
    }

    /**
     * 工厂模式，创建实例
     *
     * @param bean
     * @param <E>
     * @return
     */
    public static <E> SolrBeanHelper<E> instanicate(Class<E> bean){
        //java的范型像语法糖，编译时存在，运行时就被擦除了
        //所以我们把它丢到Class<T> bean
        return new SolrBeanHelper<E>(bean);
    }

    /**
     * 设置高亮字段
     *
     * @param fields
     */
    public SolrBeanHelper<T> setHighlightFields(String... fields){
        mHighlightFields = new ArrayList<String>(Arrays.asList(fields));
        mSolrQuery.setParam("hl.fl", fields);
        return this;
    }

    /**
     * 设置查询的字段，没有默认反射实体类上的注解
     *
     * @param fields
     * @return
     */
    public SolrBeanHelper<T> setQueryFields(String... fields){
        mQueryFields = new ArrayList<String>(Arrays.asList(fields));
        return this;
    }

    public SolrBeanHelper<T> query(SolrServer server)throws Exception{
        mMap = reflectQueryFields();

        //没有设置mQueryFields，默认全部Fields
        if (mQueryFields == null || mQueryFields.size()==0){
            mQueryFields = new ArrayList<String>(mMap.keySet());
        }
        if(analysable)
            mDesignateFields = analyseQueryString(query);  //指定查询字段
        mDesignateHighligntFields = new ArrayList<String>();  //指定高亮字段
        mSolrQuery.setParam("q", multipartQueryField(query, mQueryFields, mDesignateFields, mDesignateHighligntFields));

        if (mHighlightFields == null || mHighlightFields.size()==0){
            mHighlightFields = new ArrayList<String>(mMap.keySet());
        }

        mSolrServer = server;
        response = server.query(mSolrQuery);
        mSolrList = response.getResults();
        mDocumentCount = mSolrList.getNumFound();
        return this;
    }

    /**
     * 得到搜索结果
     * @param server
     * @return
     * @throws Exception
     */
    public List<T> getResults() throws Exception {
        List<T> result = new ArrayList<T>();
        ids = new ArrayList<String>();

        for (SolrDocument item : mSolrList){
            String key = (String) item.getFieldValue("id");
            ids.add(key);
            T bean = (T) this.bean.newInstance();
            for (String field : mQueryFields){
                Method method = mMap.get(field);

                String o = (isHighlight && mHighlightFields.contains(field)
                        ? (mDesignateHighligntFields != null && mDesignateHighligntFields.size()!=0
                        ? mDesignateHighligntFields.contains(field)
                        : true)
                        : false)
                        ? getHighlighting(item, key, field)
                        : (String) item.getFieldValue(field);
                Class t = method.getParameterTypes()[0];
                if (t == Integer.class)
                    method.invoke(bean, Integer.valueOf(o));
                else if (t == Long.class)
                    method.invoke(bean, Long.valueOf(o));
                else if (t == Date.class)
                    method.invoke(bean, new SimpleDateFormat().parse(o));
                else
                    method.invoke(bean, o);
            }
            result.add(bean);
        }
        return result;
    }

    /**
     * 反射出对象set方法上的Solr注解，得到对应的field name
     * @param bean
     * @param <T>
     * @return
     * @throws Exception
     */
    public Map<String, Method> reflectQueryFields(){
        Map<String, Method> result = new HashMap<String, Method>();
        for (Field field : bean.getDeclaredFields()){
            try {
                final String mFieldName = field.getName();
                char[] chars = mFieldName.toCharArray();
                chars[0] -= 32;
                String mFiledSetMethodName = "set" + new String(chars);
                Method method = bean.getMethod(mFiledSetMethodName, field.getType());
                org.apache.solr.client.solrj.beans.Field f = method.getAnnotation(org.apache.solr.client.solrj.beans.Field.class);
                if (f != null && (mQueryFields == null || mQueryFields.size() == 0 || mQueryFields.contains(f.value()))){
                    result.put(f.value(), method);
                }
            }catch (NoSuchMethodException e){

            }
        }
        return result;
    }

    /**
     * 初始化
     * @param q
     * @param highlight
     * @param pageNum
     * @param pageSize
     * @return
     */
    public SolrBeanHelper<T> initQuery(String q,boolean highlight, int pageNum, int pageSize){
        return highlight
                ? initQueryHighlight(q, pageNum, pageSize, 200)
                : initQueryNotHighlight(q, pageNum, pageSize);
    }

    /**
     *
     * @param q
     * @param pageNum
     * @param pageSize
     * @return
     */
    public SolrBeanHelper<T> initQueryHighlight(String q, int pageNum, int pageSize, int fragsize){
        query = q;
        isHighlight = true;
        mSolrQuery = new SolrQuery(q)
                .setHighlight(true)
                .setHighlightSimplePre("<span class='highlight'>")
                .setHighlightSimplePost("</span>")
                .setHighlightSnippets(1)
                .setHighlightFragsize(fragsize);
        if (pageNum>0)
            mSolrQuery.setStart((pageNum - 1) * pageSize);
        if (pageSize>0)
            mSolrQuery.setRows(pageSize);
        return this;
    }

    /**
     * 是否对查询字符串做字段抽取分析 field:value
     * @param analysable
     */
    public void setAnalysable(boolean analysable) {
        this.analysable = analysable;
    }

    /**
     *
     * @param q
     * @param pageNum
     * @param pageSize
     * @return
     */
    public SolrBeanHelper<T> initQueryNotHighlight(String q, int pageNum, int pageSize){
        query = q;
        isHighlight = false;
        mSolrQuery =  new SolrQuery(q);
        if (pageNum>0)
            mSolrQuery.setStart((pageNum - 1) * pageSize);
        if (pageSize>0)
            mSolrQuery.setRows(pageSize);
        return this;
    }

    /**
     * 查询出来的记录数量
     * @return
     */
    public int getDocumentCount(){
        return (int) mDocumentCount;
    }

    /**
     * 设置指定查询的字段
     * @param map
     */
    public void setDesignateFields(Map<String, List<String>> map){
        this.mDesignateFields = map;
    }

    /**
     *
     * @param query
     * @param fields
     * @return
     */
    public static String multipartQueryField(String query, List<String> fields,
                                             Map<String, List<String>> designates, List<String> mDesignateHighligntFields){
        StringBuilder buffer = new StringBuilder();
        if (fields!=null && fields.size()>0){
            for (String field : fields){

                //是否有指定查询的字段
                if (designates != null){
                    int _index = field.indexOf("_");
                    //可能有下划线分割，也可能没有
                    String item = _index == -1 ? field : field.substring(_index+1);

                    List<String> values = designates.get(item);
                    if (values==null || values.size()==0){
                        values = designates.get(OTHER_FIELD);
                    }
                    if (values!=null && values.size()>0){
                        mDesignateHighligntFields.add(field);
                        for (String value : values){
                            buffer.append(field)
                                    .append(":")
                                    .append(value)
                                    .append(" + ");
                        }
                    }
                }else {
                    buffer.append(field)
                            .append(":")
                            .append(query)
                            .append(" + ");
                }
            }
            if (buffer.toString().endsWith(" + "))
                buffer.delete(buffer.lastIndexOf(" + "), buffer.length());
            return buffer.toString();
        }
        return "";
    }

    /**
     * 设置高亮字段
     *
     * @param response
     * @param doc
     *@param key
     * @param field   @return
     */
    public String getHighlighting(SolrDocument doc, String key, String field) {
        if (response.getHighlighting().get(key).get(field)!=null)
            return response.getHighlighting().get(key).get(field).get(0);
        else
            return (String) doc.getFieldValue(field);
    }

    /**
     * 解析高级查询的字段
     *
     * @param query
     * @return
     */
    public static Map<String, List<String>> analyseQueryString(String query){
        if (StringUtils.isEmpty(query))
            return null;

        Map<String, List<String>> map = new HashMap<String, List<String>>();
        String[] queries = query.split(" +");
        Pattern pattern = Pattern.compile("([^:]+):([^:]+)");
        Matcher matcher = null;

        for (String item : queries){
            matcher = pattern.matcher(item);
            String key = OTHER_FIELD;
            String value = item;
            if (matcher.find()){
                key = matcher.group(1);
                value = matcher.group(2);
            }
            if (map.get(key) == null){
                List<String> values = new ArrayList<String>();
                values.add(value);
                map.put(key, values);
            }else {
                map.get(key).add(value);
            }
        }
        return map;
    }

}
